<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
  <meta charset="UTF-8">
  <title>Vuetify Components::Thymeleaf</title>
</head>
<body>
  <!-- ######################## -->
  <!--/* 带Confirm提示的按钮组件 */-->
  <!-- ######################## -->
  <th:block th:fragment="v-btn-confirm">
    <template id="v-btn-confirm-template">
      <v-menu location="top" :close-on-content-click="false" :disabled="disabled" v-model="visible">
        <template v-slot:activator="{ props }">
          <a v-bind="props" :class="fclass" ref="button">
            <slot>{{$t('删除')}}</slot>
          </a>
        </template>
        <v-list style="padding: 0;">
          <v-list-item>
            <v-list-item-title class="text-center" style="font-size: 14px;">
              <v-icon color="warning">{{icon}}</v-icon><span class="confrim-message" v-html="$t(message)"></span>
            </v-list-item-title>
            <v-list-item-action class="justify-center">
              <v-btn border size="small" variant="text" class="mr-2" :disabled="loading" @click="cancel">{{$t(cancelText)}}</v-btn>
              <v-btn size="small" color="primary" :loading="loading" @click="confirm">{{$t(confirmText)}}</v-btn>
            </v-list-item-action>
          </v-list-item>
        </v-list>
      </v-menu>
    </template>
    <script type="text/javascript">
      const VButtonConfirmComponent = Vue.defineComponent({
        name: 'VButtonConfirm',
        template: '#v-btn-confirm-template',
        data: function() {
          return {
            visible: false,
            loading: false,
            manual: false
          }
        },
        computed: {
          fclass() {
            return ((this.clazz??'') + (this.visible? ' op-deleting':'')) || undefined;
          }
        },
        watch: {
          visible(v) {
            let eventType = null;
            if (v) {
              this.manual = this.loading? this.manual : false;
              eventType = "open";
            } else {
              if (!this.manual && !this.loading) {
                eventType = "close";
              }
            }
            if (eventType) this.$emit(v?"open":"close", this.data, this);
            this.toggleHighlight(v);
          }
        },
        props: {
          message: {
            type: String,
            default: '确定删除？'
          },
          icon: {
            type: String,
            default: 'mdi-message-question'
          },
          confirmText: {
            type: String,
            default: '确定'
          },
          cancelText: {
            type: String,
            default: '取消'
          },
          disabled: {
            type: Boolean,
            default: false
          },
          clazz: {
            type: String
          },
          data: Object
        },
        methods: {
          confirm() {
            this.loading = true;
            this.manual = true;
            this.$emit("confirm", this.data, this);
            window.confirmObj = this;
          },
          cancel() {
            this.manual = true;
            this.close();
            this.$emit("cancel", this.data, this);
          },
          close() {
            this.visible = false;
          },
          finish() {
            this.close();
            this.loading = false;
          },
          toggleHighlight(v) {
            let hclazz = 'v-highlight';
            let parentNode = this.$refs.button.parentNode.parentNode;
            if (parentNode) {
              if (parentNode.classList.contains('v-data-table__expanded__content')) {
                let sibling = parentNode.previousElementSibling;
                if (sibling && sibling.classList.contains('v-data-table__expanded__row')) {
                  sibling.classList.toggle(hclazz)
                }
              } else {
                if (parentNode.classList.contains('row')) { //TreeGrid
                  while (parentNode) {
                    if (parentNode.classList.contains('v-treeview-node__root')) {
                      this.$nextTick(() => {
                        v? parentNode.classList.add(hclazz) : parentNode.classList.remove(hclazz);
                      })
                      break;
                    }
                    parentNode = parentNode.parentNode;
                  }
                } else { //Datatable
                  parentNode.classList.toggle(hclazz);
                }
              }
            }
          }
        }
      })
      if (typeof(baseApp) === 'object') {
        baseApp.components['v-btn-confirm'] = VButtonConfirmComponent;
      }
    </script>
  </th:block>

  <!-- ################# -->
  <!--/* 自定义下拉树组件 */-->
  <!-- ################# -->
  <th:block th:fragment="v-tree-select">
    <template id="v-tree-select-template">
      <v-menu :close-on-content-click="!multiple">
        <template v-slot:activator="{ props }">
          <v-text-field v-bind="props" :model-value="text" :hide-details="errorMessages.length === 0" readonly :loading="loading" :clearable="clearable" :error-messages="errorMessages" :label="label" @update:model-value="clearItems" :class="clazz"></v-text-field>
        </template>
        <v-list>
          <v-list-item>
            <v-list-item-title>
              <v-treeview
                  mandatory
                  activatable
                  :active-strategy="activeStrategy"
                  color="primary"
                  bg-color="transparent"
                  density="compact"
                  max-height="300"
                  :item-value="itemValue"
                  :item-title="itemTitle"
                  :items="innerItems"
                  v-model:opened="openedItems"
                  v-model:activated="activatedItems"
              >
              </v-treeview>
            </v-list-item-title>
          </v-list-item>
        </v-list>
      </v-menu>
    </template>
    <script type="text/javascript">
      const VTreeSelectComponent = Vue.defineComponent({
        name: 'VTreeSelect',
        template: '#v-tree-select-template',
        data() {
          return { // 组件内属性，可读可写
            loading: false,
            innerItems: [],
            openedItems: [],
            activatedItems: []
          }
        },
        props: { // 组件内，这些属性为只读，接收父组件数据，符合VUE单向数据流设计原则
          label: {type: String, default: ""},
          items: {type: Array, default: []},
          itemValue: {type: String, default: "orgId"},
          itemTitle: {type: String, default: "orgName"},
          parentItemValue: {type: String, default: "parentId"},
          mandatory: {type: Boolean, default: false},
          temporary: {type: Boolean, default: false},
          multiple: {type: Boolean, default: false},
          clearable: {type: Boolean, default: false},
          clazz: {type: String, default: null},
          callback: {type: Function, default: () => {}},
          errorMessages: {type: Array, default: []},
          storageKey: {type: String, default: 'stenantId'},
          modelValue: {type: Array, default: []}
        },
        emits: ['update:modelValue'],
        watch: { // 可同时监听 props 和 data 中的属性
          items(val) {
            this.innerItems = this.wrapedTree(val);
            this.loading = false;
            this.callback(this.innerItems, val);
          },
          modelValue(val) {
            this.activatedItems = val;
          },
          activatedItems(val) {
            this.$emit('update:modelValue', val);
            if (this.openedItems.length === 0) {
              this.findOpenedNodes();
            }

            // 记忆选中的节点ID
            if(!this.temporary && this.storageKey) {
              if (val?.length > 0) {
                $cookies.set(this.storageKey, val[0], 0);
              } else {
                $cookies.remove(this.storageKey);
              }
            }
          },
        },
        computed: {
          activeStrategy() {
            return this.multiple? 'independent' : 'single-independent';
          },
          enableLoad() {
            return this.$attrs.onLoad? true : false;
          },
          inenerItemsMap() {
            let inenerItemsMap = {};
            for (let item of this.items) {
              inenerItemsMap[item[this.itemValue]] = item;
            }
            return inenerItemsMap;
          },
          text() {
            let text = "";
            if (this.items?.length > 0) {
              for (let id of this.activatedItems) {
                let item = this.inenerItemsMap[id];
                text += item[this.itemTitle] + ",";
              }
            }
            return text.substring(0, text.length - 1);
          }
        },
        mounted() {
          this.innerItems = this.wrapedTree(this.items || this.innerItems);
          if (this.modelValue && this.modelValue.length > 0) {
            this.activatedItems = this.modelValue;
          }
          if (this.enableLoad) {
            this.loading = true;
            this.$emit('load');
          }
        },
        methods: {
          wrapedTree(val) { // 把列表数据包装为树结构数据
            if (val?.length > 0) {
              this.findOpenedNodes();
              return baseApp.methods.wrapTreeData(val, this.itemValue, null, this.parentItemValue);
            }
            return val;
          },
          findOpenedNodes() {
            if (this.items?.length > 0 && this.activatedItems?.length > 0) {
              for (let id of this.activatedItems) {
                this.findParents(id);
              }
            } else {
              this.openedItems = [];
            }
          },
          findParents(id) { // 查找所有父节点
            let item = this.inenerItemsMap[id];
            if (item) {
              let parentId = item[this.parentItemValue];
              if (parentId && id.length === parentId.length) {
                this.openedItems.push(parentId);
                this.findParents(parentId);
              }
            }
          },
          clearItems() {
            if (!this.mandatory) {
              this.activatedItems = [];
              this.openedItems = [];
            }
          }
        }
      })

      if (typeof(baseApp) === 'object') {
        baseApp.components['v-tree-select'] = VTreeSelectComponent;
      }
    </script>
  </th:block>

  <!-- ######################### -->
  <!--/* 自定义全屏Loading提示插件 */-->
  <!-- ######################### -->
  <th:block th:fragment="v-loading">
    <template id="v-loading-template">
      <v-overlay v-model="value" class="align-center justify-center" :opacity="0.5">
        <v-row justify="center" class="fill-height" style="width: 80vw">
          <v-col cols="12" class="text-subtitle-1 text-center text-white">
            {{text || $vuetify.locale.t('$vuetify.loading')}}
          </v-col>
          <v-col cols="4">
            <v-progress-linear color="deep-purple-accent-4" height="6" indeterminate rounded></v-progress-linear>
          </v-col>
        </v-row>
      </v-overlay>
    </template>
    <script type="text/javascript">
      const VLoadingComponent = Vue.defineComponent({
        name: 'VLoading',
        template: '#v-loading-template',
        props: {
          value: {
            type: Boolean,
            default: false
          },
          text: {
            type: String,
          }
        }
      })
      if (typeof(baseApp) === 'object') {
        baseApp.components['v-loading'] = VLoadingComponent;
      }
    </script>
  </th:block>

  <!-- ###################### -->
  <!--/* 支持全屏展开的Card组件 */-->
  <!-- ###################### -->
  <th:block th:fragment="v-card-zoom">
    <template id="v-card-zoom-template">
      <v-card :class="{'v-card--max':isMax}" @mouseover="toolbar=true" @mouseout="toolbar=false" :color="color" :flat="flat" :disabled="disabled" :rounded="rounded" :loading="loading" :elevation="elevation" :width="width" :height="height" :max-width="maxWidth" :min-width="minWidth" :max-height="maxHeight" :min-height="minHeight" :tile="tile">
        <div style="position:absolute; right:0; top:0; z-index:9">
          <v-btn icon="mdi-refresh" size="x-small" variant="text" v-if="enableRefresh" v-show="toolbar" :color="iconColor" @click="refresh">
          </v-btn>
          <v-btn :icon="iconZoom" size="x-small" variant="text" v-show="toolbar" :color="iconColor" @click="zoom">
          </v-btn>
        </div>
        <slot></slot>
      </v-card>
    </template>
    <script type="text/javascript">
      const VCardZoomComponent = Vue.defineComponent({
        name: 'VCardZoom',
        template: '#v-card-zoom-template',
        data() {
          return {
            iconZoom: 'mdi-arrow-expand',
            toolbar: false,
            isMax: false
          }
        },
        computed: {
          enableRefresh() {
            const attrs = this.$attrs;
            return attrs && attrs.onRefresh? true : false;
          }
        },
        props:{
          iconColor:String,
          flat:Boolean,
          disabled:Boolean,
          tile:Boolean,
          color:String,
          loading:{type:Boolean|String,default:false},
          rounded:{type:Boolean|String,default:false},
          elevation:{type:Number|String,default:undefined},
          width:{type:Number|String,default:undefined},
          maxWidth:{type:Number|String,default:undefined},
          minWidth:{type:Number|String,default:undefined},
          height:{type:Number|String,default:undefined},
          maxHeight:{type:Number|String,default:undefined},
          minHeight:{type:Number|String,default:undefined}
        },
        methods: {
          zoom() {
            this.isMax = !this.isMax;
            this.iconZoom = this.isMax? "mdi-arrow-collapse" : "mdi-arrow-expand";
            this.$nextTick(() => {
              this.$emit("zoom", this.isMax);
            });
          },
          refresh() {
            this.$emit("refresh");
          }
        }
      })
      if (typeof(baseApp) === 'object') {
        baseApp.components['v-card-zoom'] = VCardZoomComponent;
      }
    </script>
  </th:block>

  <!-- ################# -->
  <!--/* 下拉日期控件 */-->
  <!-- ################# -->
  <th:block th:fragment="v-date-picker-select">
    <template id="v-date-picker-select-template">
      <v-menu v-model="vmenu" :close-on-content-click="false">
        <template v-slot:activator="{ props }">
          <v-text-field v-bind="props" :model-value="modelValue" prepend-inner-icon="mdi-calendar" hide-details readonly :clearable="clearable" :label="label" @update:model-value="clearDate">
            <template #prepend v-if="prependIcon">
              <v-icon :icon="prependIcon" size="x-small" class="mr-n4"></v-icon>
            </template>
          </v-text-field>
        </template>
        <v-date-picker v-model="vdate" hide-header show-adjacent-months color="primary" :min="min" :max="max" class="v-date-picker-select">
          <template v-slot:actions>
            <v-btn color="primary" @click="yesterday">{{$t('昨天')}}</v-btn>
            <v-btn color="primary" @click="today">{{$t('今天')}}</v-btn>
          </template>
        </v-date-picker>
      </v-menu>
    </template>
    <script type="text/javascript">
      const VDatePickerSelectComponent = Vue.defineComponent({
        name: 'VDatePickerSelect',
        template: '#v-date-picker-select-template',
        data() {
          return {
            vdate: null,
            vmenu: false
          }
        },
        props: { // 组件内，这些属性为只读，接收父组件数据，符合VUE单向数据流设计原则
          label: {type: String},
          min: {type: String},
          max: {type: String},
          prependIcon: {type: String},
          clearable: {type: Boolean, default: false},
          modelValue: {type: String}
        },
        emits: ['update:modelValue'],
        watch: { // 可同时监听 props 和 data 中的属性
          vdate(val) {
            this.vmenu = false;
            let dateText = val;
            if (val instanceof Date) {
              dateText = val.format('yyyy-MM-dd');
            }
            this.$emit('update:modelValue', dateText);
          },
          modelValue(val) {
            this.vdate = val;
          },
        },
        mounted() {
          this.vdate = this.modelValue || this.vdate;
        },
        methods: {
          today() {
            this.vdate = new Date();
          },
          yesterday() {
            let d = new Date();
            d.setDate(d.getDate() -1);
            this.vdate = d;
          },
          clearDate(val) {
            this.vdate = null;
          },
        }
      });
      if (typeof(baseApp) === 'object') {
        baseApp.components['v-date-picker-select'] = VDatePickerSelectComponent;
      }
    </script>
  </th:block>
</body>
</html>